//
// JavaCompiler
//
// Copyright (C) jextra.net.
//
//  This file is part of the Crack build system.
//
//  The Crack build system is free software; you can redistribute it 
//  and/or modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The Crack build system is distributed in the hope that it will be 
//  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the Crack build system; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.crack.tool;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import net.jextra.crack.*;
import net.jextra.crack.type.*;

/**
 * <p>
 * Wrapper tool to the javac compiler.
 * </p>
 */
public class JavaCompiler
{
    // ============================================================
    // Fields
    // ============================================================

    public static final String SUFFIX_JAVA = ".java";
    public static final String SUFFIX_CLASS = ".class";

    private List<File> sourceDirs;
    private List<File> sourceFiles;
    private File outputDir;
    private Set<File> dependencies;
    private boolean checkTimestamps;

    // ============================================================
    // Constructors
    // ============================================================

    public JavaCompiler()
    {
        sourceDirs = new ArrayList<File>();
        sourceFiles = new ArrayList<File>();
        checkTimestamps = true;
        dependencies = new LinkedHashSet<File>();
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    public boolean getCheckTimestamps()
    {
        return checkTimestamps;
    }

    public void setCheckTimestamps( boolean checkTimestamps )
    {
        this.checkTimestamps = checkTimestamps;
    }

    public List<File> getSourceDirs()
    {
        return sourceDirs;
    }

    public void addSourceDir( File dir )
    {
        assert dir.isDirectory() : "Source dir '" + dir + "' must be an existing directory";
        sourceDirs.add( dir );
    }

    public File getOutputDir()
    {
        return outputDir;
    }

    public void setOutputDir( File dir )
    {
        assert dir.isDirectory() : "Output dir '" + dir + "' must be an existing directory";
        outputDir = dir;
    }

    public void addDependency( File file )
    {
        if ( file == null )
        {
            return;
        }

        dependencies.add( file );
    }

    public void addDependencies( Collection<File> files )
    {
        if ( files == null )
        {
            return;
        }

        dependencies.addAll( files );
    }

    public String getClasspath()
        throws IOException
    {
        StringBuilder builder = new StringBuilder( outputDir.getCanonicalPath() );
        for ( File file : dependencies )
        {
            builder.append( File.pathSeparatorChar );
            builder.append( file.getCanonicalPath() );
        }
        return builder.toString();
    }

    public int compile( FileSet files )
        throws CrackException
    {
        for ( File file : files )
        {
            sourceFiles.add( file );
        }
        return compile();
    }

    public int compile()
        throws CrackException
    {
        ArrayList<String> args = new ArrayList<String>();
        try
        {
            StringBuilder sourcePath = new StringBuilder();
            for ( File dir : sourceDirs )
            {
                if ( sourcePath.length() > 0 )
                {
                    sourcePath.append( ";" );
                }
                sourcePath.append( dir.getCanonicalPath() );
            }

            if ( outputDir != null )
            {
                args.add( "-sourcepath" );
                args.add( sourcePath.toString() );
                args.add( "-d" );
                args.add( outputDir.getCanonicalPath() );
                String classpath = getClasspath();
                if ( classpath != null && !classpath.isEmpty() )
                {
                    args.add( "-cp" );
                    args.add( classpath );
                }
            }

            List<File> sourceFiles = getSourceFiles();
            for ( File file : sourceFiles )
            {
                args.add( file.getCanonicalPath() );
            }

            // Nothing to compile
            if ( sourceFiles.isEmpty() )
            {
                System.out.println( "All java files are already compiled." );
                return 0;
            }
            else
            {
                System.out.printf( "Compiling %d java files.\n", sourceFiles.size() );
            }
        }
        catch ( IOException ex )
        {
            throw new CrackException( ex );
        }

        int result;
        try
        {
            //
            // First attempt to run within this JVM.
            //
            result = compileInJVM( args );
        }
        catch ( Throwable ex )
        {
            //
            // Now run as fork.
            //
            ex.printStackTrace();

            // result = compileFork( args );
            result = -1;
        }

        return result;
    }

    public List<File> getSourceFiles()
        throws IOException
    {
        ArrayList<File> list = new ArrayList<File>();
        JavaPackageFinder finder = new JavaPackageFinder();
        for ( File javaFile : sourceFiles )
        {
            if ( !javaFile.getName().endsWith( SUFFIX_JAVA ) )
            {
                continue;
            }

            //
            // Check timestamp
            //
            if ( checkTimestamps )
            {
                finder.read( javaFile );
                if ( finder.getPath() != null )
                {
                    File classDir = new File( outputDir, finder.getPath() );
                    File classFile = new File( classDir, javaFile.getName().replace( SUFFIX_JAVA, SUFFIX_CLASS ) );
                    if ( javaFile.lastModified() <= classFile.lastModified() )
                    {
                        continue;
                    }
                }
            }

            list.add( javaFile );
        }

        // ArrayList<File> list = new ArrayList<File>();
        // for ( File sourceDir : _sourceDirs )
        // {
        // FileSet files = new StrainedFileSet( sourceDir, _strainer );
        // for ( File javaFile : files )
        // {
        // if ( !javaFile.getName().endsWith( SUFFIX_JAVA ) )
        // {
        // continue;
        // }
        //
        // String path = javaFile.getCanonicalPath().substring( sourceDir.getCanonicalPath().length() );
        // File classFile = new File( _outputDir, path.replace( SUFFIX_JAVA, SUFFIX_CLASS ) );
        //
        // if ( !_checkTimestamps || javaFile.lastModified() > classFile.lastModified() )
        // {
        // list.add( javaFile );
        // }
        // }
        // }

        return list;
    }

    // ----------
    // private
    // ----------

    private int compileInJVM( ArrayList<String> args )
        throws Exception
    {
        Class<?> javacClass = Class.forName( "com.sun.tools.javac.Main" );
        Method method = javacClass.getMethod( "compile", new Class[]
        { String[].class } );
        Object instance = javacClass.newInstance();

        String[] strings = new String[args.size()];
        for ( int i = 0; i < args.size(); i++ )
        {
            strings[i] = args.get( i );
        }

        int result = (Integer) method.invoke( instance, new Object[]
        { strings } );

        return result;
    }

    // private int compileFork( ArrayList<String> args )
    // throws Exception
    // {
    // args.add( 0, "javac" );
    // for ( String arg : args )
    // {
    // System.out.println( arg );
    // }
    // return _fork.execute( args );
    // }
}
